package com.tjc.spring.core;

import com.tjc.spring.core.annotation.Autowird;
import com.tjc.spring.mvc.annotataion.Repository;
import com.tjc.spring.util.BeanNameUtil;
import com.tjc.spring.web.annotation.Controller;
import com.tjc.spring.web.annotation.Service;

import java.io.File;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

//getBean createBean registBean
public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry {

    public Map<String, Object> beanContainer = new ConcurrentHashMap<>();

    public Map<String, GenericBeanDefinition> definitionMap = new HashMap<>();

    private static final Class[] COMPONENT_ANNO = {Controller.class, Service.class, Repository.class};
    private Class<?> clazz;


    //TODO 递归注入，还能优化，依赖循环没有解决
    public Object getBean(String beanId) {
        Object bean = beanContainer.get(beanId);
        if (bean == null) {
            try {
                bean = createBean(beanId);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
        beanContainer.put(beanId, bean);
        return bean;
    }

    @Override
    public GenericBeanDefinition getBeanDefinition(String beanId) {
        return definitionMap.get(beanId);
    }

    private Object createBean(String beanName) throws IllegalAccessException, InstantiationException {
        Object bean = null;
        GenericBeanDefinition bd = getBeanDefinition(beanName);
        bean = initializeBean(beanName, bd);
        injectProperty(bean, bd);
        beanContainer.put(beanName, bean);
        return bean;
    }

    private Object initializeBean(String beanName, GenericBeanDefinition bd) {
        ConstructorResolver resolver = new ConstructorResolver(this);
        return resolver.autowireConstructor(bd);
    }

    /**
     * 注入属性
     *
     * @param bean
     */
    private void injectProperty(Object bean, GenericBeanDefinition bd) throws InstantiationException, IllegalAccessException {
        PropertyResolver resolver = new PropertyResolver(this);
        resolver.autowirePorperty(bean, bd);

        for (Field field : bean.getClass().getDeclaredFields()) {
            String fieldName = field.getName();
            if (field.isAnnotationPresent(Autowird.class)) {
                field.setAccessible(true);
                Object injectBean = this.beanContainer.get(fieldName);
                if (injectBean == null) {
                    injectBean = createBean(fieldName);
                    beanContainer.put(fieldName, injectBean);
                }
                field.set(bean, injectBean);
                field.setAccessible(false);
            }
        }
    }

    /**
     * 扫描包获取bean的全路径名
     *
     * @param packages
     * @return
     */
    public List<String> scanPackages(String... packages) {
        List<String> classList = new ArrayList<>();
        Arrays.stream(packages).forEach(packageName -> {
            classList.addAll(scanPackage(packageName));
        });
        return classList;
    }

    private List<String> scanPackage(String classOrPackage) {
        List<String> classList = new LinkedList<>();
        if (classOrPackage.endsWith(".class")) {
            classList.add(classOrPackage.substring(0, classOrPackage.lastIndexOf(".class")));
            return classList;
        }
//        String packageDir = packageName.replaceAll("\\.",Matcher.quoteReplacement(File.separator));
        String packageDir = classOrPackage.replaceAll("\\.", "/");
        String classpath = this.getClass().getClassLoader().getResource(packageDir).getFile().substring(1);
        File classDir = new File(classpath);
        if (classDir.isDirectory()) {
            File[] childFiles = classDir.listFiles();
            for (File child : childFiles) {
                classList.addAll(scanPackage(classOrPackage + "." + child.getName()));
            }
        } else {
            classList.add(classOrPackage + "." + classDir.getName());
        }
        return classList;
    }

    public static boolean isBean(String className) {
        try {
            Class clazz = Class.forName(className);
            for (Class anno : COMPONENT_ANNO) {
                if (clazz.isAnnotationPresent(anno))
                    return true;
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }

    public DefaultBeanFactory(String... packages) {
        List<String> beanClassList = scanPackages(packages);
        for (String beanClass : beanClassList) {
            classMapping(beanClass);
        }
    }

    private void classMapping(String className) {
        String beanId = null;
        Class clazz = null;
        if (isBean(className)) {
            try {
                clazz = Class.forName(className);
                String SimpleClassName = className.substring(className.lastIndexOf(".") + 1, className.length());
                //TODO 优化
                if (clazz.isAnnotationPresent(Controller.class)) {
                    Controller controllerAnno = (Controller) clazz.getAnnotation(Controller.class);
                    String beanName = controllerAnno.value();
                    if ("".equals(beanName))
                        beanId = BeanNameUtil.lowerCaseFirst(SimpleClassName);
                    else
                        beanId = beanName;
                }
                if (clazz.isAnnotationPresent(Service.class)) {
                    Service serviceAnno = (Service) clazz.getAnnotation(Service.class);
                    String beanName = serviceAnno.value();
                    if ("".equals(beanName))
                        beanId = BeanNameUtil.lowerCaseFirst(SimpleClassName);
                    else
                        beanId = beanName;
                }
                if (clazz.isAnnotationPresent(Repository.class)) {
                    Repository serviceAnno = (Repository) clazz.getAnnotation(Repository.class);
                    String beanName = serviceAnno.value();
                    if ("".equals(beanName))
                        beanId = BeanNameUtil.lowerCaseFirst(SimpleClassName);
                    else
                        beanId = beanName;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //todo error
            GenericBeanDefinition bd = new GenericBeanDefinition(beanId, className, clazz);
            definitionMap.put(beanId, bd);
        }
    }

    @Override
    public void register(String id, GenericBeanDefinition db) {
        if (definitionMap.containsKey(id)) {
            throw new RuntimeException("multi bean id: " + id);
        }
        definitionMap.put(id, db);
    }
}
